﻿using System;
using System.Linq;
using System.Collections.Generic;
using System.Text;
using WiMo.Games.Drawables;
using System.Drawing;

namespace WiMo.Games.OpenGLDisplay
{
    public class RenderTree
    {
        public RenderTarget Root { get; private set; }
        public static List<RenderTarget> RenderTargets = new List<RenderTarget>();
        public static Dictionary<IDrawable, List<RenderTarget>> TargetLooksup = new Dictionary<IDrawable, List<RenderTarget>>();

        public static List<RenderTarget> GetRenderTarget(IDrawable sprite)
        {
            List<RenderTarget> targets;
            if (RenderTree.TargetLooksup.ContainsKey(sprite))
                targets = RenderTree.TargetLooksup[sprite];
            else
            {
                targets = new List<RenderTarget>();
                RenderTree.TargetLooksup.Add(sprite, targets);
            }

            return targets;
        }

        public Size Size
        {
            get;
            set;
        }

        public Size RenderTargetSize
        {
            get;
            set;
        }

        private static List<RenderTarget> GetTempDirty(List<RenderTarget> rawDirtyRects)
        {            
            var rects = new List<RenderTarget>();
            var added = new List<RenderTarget>();
            RenderTarget prev = null;
            RenderTarget first = null;

            foreach (var rect in rawDirtyRects)
            {
                if (!added.Contains(rect))
                {
                    var newRenderTarget = new RenderTarget();
                    newRenderTarget.Sprites.AddRange(rect.Sprites);
                    first = rect;
                    prev = rect;
                    while (prev.Next != null && prev.Next.IsDirty)
                    {                        
                        prev = prev.Next;
                        foreach(var sprite in prev.Sprites) 
                            if (!newRenderTarget.Sprites.Contains(sprite))
                                newRenderTarget.Sprites.Add(sprite);

                        added.Add(prev);
                    }

                    newRenderTarget.BoundingRect = new Rectangle(first.BoundingRect.X, first.BoundingRect.Y, prev.BoundingRect.Right - first.BoundingRect.X, prev.BoundingRect.Bottom - first.BoundingRect.Y);
                    
                    rects.Add(newRenderTarget);
                }
            }
        
            return rects;
        }

        private static List<RenderTarget> CombineDirtyRects(List<RenderTarget> rects)
        {
            var dirtyRects = new List<RenderTarget>();

            for (var x = 0; x < rects.Count - 1; ++x)
            {
                var topLeft = new Location(rects[x].BoundingRect.Left, rects[x].BoundingRect.Top);
//                var dirtyRect = new RenderTarget();
//                dirtyRect.Sprites.AddRange(rects[x].Sprites);

                /*while (x < rects.Count - 1 &&
                    rects[x].BoundingRect.Left == rects[x + 1].BoundingRect.Left &&
                    rects[x].BoundingRect.Right == rects[x + 1].BoundingRect.Right &&
                    rects[x].BoundingRect.Bottom == rects[x + 1].BoundingRect.Top)
                {
                    x++;
                    foreach (var sprite in rects[x].Sprites)
                        if (!dirtyRect.Sprites.Contains(sprite))
                            dirtyRect.Sprites.Add(sprite);                    
                }*/

                //dirtyRect.BoundingRect = new Rectangle(topLeft.X, topLeft.Y, rects[x].BoundingRect.Right - topLeft.X, rects[x].BoundingRect.Bottom - topLeft.Y);

                //dirtyRects.Add(dirtyRect);

//                if (x == rects.Count - 2)
//                    dirtyRects.Add(rects[rects.Count - 1]);
            }
            
            return dirtyRects;
        }

        public List<RenderTarget> DirtyRenderTargets
        {
            get
            {
                var targets = from rect in RenderTree.RenderTargets where rect.IsDirty orderby rect.BoundingRect.Left, rect.BoundingRect.Top select rect;                

                var tempDirty = GetTempDirty(targets.ToList());
                return tempDirty;

                if (tempDirty.Count > 1)
                    return CombineDirtyRects(tempDirty);
                else
                    return tempDirty;
            }
        }

        public List<IDrawable> DirtySprites
        {
            get
            {
                var sprites = new List<IDrawable>();

                var targets = from rect in RenderTree.RenderTargets where rect.IsDirty orderby rect.BoundingRect.Left, rect.BoundingRect.Top select rect;
                foreach(var target in targets)
                {
                    foreach (var sprite in target.Sprites)
                    {
                        if(!sprites.Contains(sprite))
                            sprites.Add(sprite);
                    }
                }

                return sprites.OrderBy(spr=>spr.ZIndex).ToList();
            }
        }

        private void CreateLevel(int maxLevel, int level, RenderTarget target)
        {
            if (level < maxLevel)
            {
                target.CreateChildren();
                CreateLevel(maxLevel, level + 1, target.NE);
                CreateLevel(maxLevel, level + 1, target.NW);
                CreateLevel(maxLevel, level + 1, target.SE);
                CreateLevel(maxLevel, level + 1, target.SW);
            }
            else
                RenderTree.RenderTargets.Add(target);
        }

        public void UpdateSprite(IDrawable sprite)
        {
            RemoveSprite(sprite);
            AddSprite(sprite);
        }

        public void AddSprite(IDrawable sprite)
        {
            if (sprite.IsDirty)
            {
                RemoveSprite(sprite);
                Root.AddSprite(sprite);
            }
            else
            {
                if (!TargetLooksup.ContainsKey(sprite))
                    Root.AddSprite(sprite);
            }
        }

        public void RemoveSprite(IDrawable sprite)
        {
            var targets = GetRenderTarget(sprite);

            foreach (var target in targets)
            {                
                target.IsDirty = true;
                target.Sprites.Remove(sprite);
            }

            TargetLooksup.Remove(sprite);
        }

        public void MarkAsClean()
        {
            Root.MarkAsClean();
        }

        public void RenderChanges(IDisplay displayManager, Graphics graphics)
        {
            var targets = DirtyRenderTargets;

            foreach (var target in targets)
                target.Render(displayManager, graphics);
        }

        public static RenderTree Create(Size size, int levels)
        {
            var tree = new RenderTree();
            
            tree.Size = size;
            tree.RenderTargetSize = new Size(size.Width / (levels * levels), size.Height / (levels * levels));
            tree.Root = new RenderTarget(new Rectangle(0, 0, size.Width, size.Height));
            tree.CreateLevel(levels, 0, tree.Root);            

            for (var x = tree.RenderTargetSize.Width / 2; x < size.Width; x += tree.RenderTargetSize.Width)
            {
                for (var y = tree.RenderTargetSize.Height / 2; y < size.Height; y += tree.RenderTargetSize.Height)
                {
                    var target = tree.FindChild(new Location(x, y));
                    target.Prev = tree.FindChild(new Location(x - tree.RenderTargetSize.Width, y));
                    target.Next = tree.FindChild(new Location(x + tree.RenderTargetSize.Width, y));
                    target.Above = tree.FindChild(new Location(x, y - tree.RenderTargetSize.Height));
                    target.Below = tree.FindChild(new Location(x, y + tree.RenderTargetSize.Height));
                }
            }

            return tree;
        }

        public RenderTarget FindChild(Location point)
        {
            if (Root.NW.BoundingRect.Contains(point))
                return Root.NW.FindLastChild(point);

            if (Root.NE.BoundingRect.Contains(point))
                return Root.NE.FindLastChild(point);

            if (Root.SE.BoundingRect.Contains(point))
                return Root.SE.FindLastChild(point);

            if (Root.SW.BoundingRect.Contains(point))
                return Root.SW.FindLastChild(point);

            return null;
        }

        private void TestIt()
        {
            var tree = RenderTree.Create(new Size(240, 320), 4);
            var x = tree.Root;
        }


    }
}
